package LDraw.Files; import java.io.File; import java.nio.CharBuffer; import java.util.ArrayList; import java.util.StringTokenizer; import javax.swing.undo.UndoManager; import LDraw.Support.DispatchGroup; import LDraw.Support.LDrawKeywords; import LDraw.Support.LDrawUtilities; import LDraw.Support.Range; import LDraw.Support.type.MessageT; import com.jogamp.common.nio.Buffers; //============================================================================== // //File: LDrawMPDModel.h // //Purpose: Represents a model which can be used as a submodel within a file. // //Created by Allen Smith on 2/19/05. //Copyright (c) 2005. All rights reserved. //============================================================================== //============================================================================== // //File: LDrawMPDModel.m // //Purpose: LDraw MPD (Multi-Part Document) models are the basic components // of an LDrawFile. An MPD model is a discreet collection of parts // (such as a car or a minifigure); each file can be composed of // multiple models. // // An MPD model is an extension of a basic LDraw model, with the // addition of a name which can be used to refer to the entire // model as a single part. (This is used, for instance, to insert // the entire minifigure driver into his car.) // // While the LDraw file format accommodates documents with only one // (non-MPD) model, Bricksmith does not make such a distinction // until the file is actually written to disk. For the sake of // simplicity, all logical models within an LDrawFile *must* be // MPD models. // // //Created by Allen Smith on 2/19/05. //Copyright 2005. All rights reserved. //============================================================================== public class LDrawMPDModel extends LDrawModel { /** * @uml.property name="modelName" */ String modelName; // ---------- model // ---------------------------------------------------[static]-- // // Purpose: Creates a new model ready to be edited. // // ------------------------------------------------------------------------------ public synchronized static LDrawMPDModel model() { LDrawMPDModel newModel = new LDrawMPDModel(); String name = null; // Set the spec-compliant model name with extension name = "UntitledModel"; newModel.setModelDisplayName(name); return newModel; }// end model // ========== init // ============================================================== // // Purpose: Creates a blank submodel. // // ============================================================================== public LDrawMPDModel init() { super.init(); modelName = ""; return this; }// end init // ========== initWithLines:inRange:parentGroup: // ================================ // // Purpose: Creates a new model file based on the lines from a file. // These lines of strings should only describe one model, not // multiple ones. // // The first line does not need to be an MPD file delimiter. If // you pass in a non-mpd submodel, this method simply wraps it in // an MPD submodel object. // // ============================================================================== public LDrawMPDModel initWithLines(ArrayList<String> lines, Range range, DispatchGroup parentGroup) { String mpdFileCommand = lines.get(range.getLocation()); String lastLine = null; CharBuffer mpdSubModelNameBuffer = Buffers.newDirectCharBuffer(256); String mpdSubmodelName = ""; boolean isMPDModel = false; boolean hasSubmodelEnd = false; Range nonMPDRange = range; // The first line should be 0 FILE modelName isMPDModel = lineIsMPDModelStart(mpdFileCommand, mpdSubModelNameBuffer); char[] tempChars = new char[mpdSubModelNameBuffer.position()]; mpdSubModelNameBuffer.position(0); mpdSubModelNameBuffer.get(tempChars); mpdSubmodelName = new String(tempChars); // Strip out the MPD commands for model parsing, and read in the model // name. if (isMPDModel == true) { // Strip out the first line and the NOFILE command, if there is // one. lastLine = lines.get(range.getMaxRange()); hasSubmodelEnd = lineIsMPDModelEnd(lastLine); if (hasSubmodelEnd) { // strip out 0 FILE and 0 NOFILE nonMPDRange = new Range(range.getLocation() + 1, range.length() - 2); } else { // strip out 0 FILE only nonMPDRange = new Range(range.getLocation() + 1, range.length() - 1); } } else { nonMPDRange = range; } // Create a basic model. super.initWithLines(lines, nonMPDRange, parentGroup); // parses model into header and steps. // If it wasn't MPD, we still need a model name. We can get that via the // parsed model. if (isMPDModel == false) { mpdSubmodelName = modelDescription(); } // And now set the MPD-specific attributes. setModelName(mpdSubmodelName); return this; }// end initWithLines:inRange: // ---------- rangeOfDirectiveBeginningAtIndex:inLines:maxIndex: // ------[static]-- // // Purpose: Returns the range from the beginning to the end of the model. // // ------------------------------------------------------------------------------ public static Range rangeOfDirectiveBeginningAtIndex(int index, ArrayList<String> lines, int maxIndex) { String firstLine = null; boolean isMPDModel = false; String currentLine = null; Range testRange = new Range(index, maxIndex - index + 1); Range modelRange = testRange; int counter = 0; int modelEndIndex = 0; if (testRange.length() > 1) { // See if we have to look for MPD syntax. firstLine = lines.get(testRange.getLocation()); isMPDModel = lineIsMPDModelStart(firstLine, null); // Find the end of the MPD model. MPD models can end with 0 // NOFILE, or // they can just stop where the next model starts. if (isMPDModel == true) { // Assume the model extends for the rest of the file unless // proven // otherwise. modelEndIndex = testRange.getMaxRange(); for (counter = testRange.getLocation() + 1; counter <= testRange .getMaxRange(); counter++) { currentLine = lines.get(counter); // if (lineIsMPDModelEnd(currentLine)) { // modelEndIndex = counter + 1; // break; // } else if (lineIsMPDModelStart(currentLine, null)) { modelEndIndex = counter - 1; break; } } modelRange = new Range(testRange.getLocation(), modelEndIndex - testRange.getLocation() + 1); } else { // Non-MPD models just go to the end of the file. modelRange = testRange; } } return modelRange; }// end rangeOfDirectiveBeginningAtIndex:inLines:maxIndex: // #pragma mark - // #pragma mark DIRECTIVES // #pragma mark - // // ========== write // ============================================================= // // Purpose: Writes out the MPD submodel, wrapped in the MPD file // commands. // // ============================================================================== public String write() { String CRLF = "\r\n";// we need a DOS line-end marker, // because // LDraw is predominantly DOS-based. String written = new String(); // Write it out as: // 0 FILE model_name // .... // model text // .... // 0 falseFILE written = written.concat(String.format("0 %s %s%s", LDrawKeywords.LDRAW_MPD_SUBMODEL_START, modelName(), CRLF)); written = written.concat(String.format("%s%s", super.write(), CRLF)); written = written.concat(String.format("0 %s", LDrawKeywords.LDRAW_MPD_SUBMODEL_END)); return written; }// end write // ========== writeModel // ============================================================= // // Purpose: Writes out the submodel, without the MPD file commands. // // ============================================================================== public String writeModel() { return super.write(); }// end writeModel // // #pragma mark - // #pragma mark DISPLAY // #pragma mark - // ========== browsingDescription // =============================================== // // Purpose: Returns a representation of the directive as a short string // which can be presented to the user. // // ============================================================================== public String browsingDescription() { return modelDisplayName(); }// end browsingDescription // ========== inspectorClassName // ================================================ // // Purpose: Returns the name of the class used to inspect this one. // // ============================================================================== public String inspectorClassName() { return "InspectionMPDModel"; }// end inspectorClassName public void setEnclosingDirective(LDrawContainer newParent) { super.setEnclosingDirective(newParent); sendMessageToObservers(MessageT.MessageScopeChanged); } // #pragma mark - // #pragma mark ACCESSORS // #pragma mark - // ========== browsingDescription // =============================================== // // Purpose: Returns a representation of the directive as a short string // which can be presented to the user. // // ============================================================================== public String modelDisplayName() { // Chop off that hideous un-Maclike .ldr extension that the LDraw File // Specification forces us to add. File f = new File(modelName); String filename = f.getName(); if (filename.contains(".")) { return filename.substring(0, filename.indexOf(".")); } else return modelName; }// end modelDisplayName // ========== modelName // ========================================================= // // Purpose: Retuns the name for this MPD file. The MPD name functions as // the part name to describe the entire submodel. // // ============================================================================== public String modelName() { return modelName; }// end modelName // ========== setModelName: // ===================================================== // // Purpose: Updates the name for this MPD file. The MPD name functions as // the part name to describe the entire submodel. // // ============================================================================== /** * @param newModelName * @uml.property name="modelName" */ public void setModelName(String newModelName) { modelName = newModelName; sendMessageToObservers(MessageT.MessageNameChanged); }// end setModelName: // ========== setModelDisplayName: // ============================================== // // Purpose: Unfortunately, we can't accept any old input for model names. // This method accepts a user-entered string with arbitrary // characters, and sets the model name to the closest // representation thereof which is still LDraw-compliant. // // After calling this method, -browsingDescription will return a // value as close to newDisplayName as possible. // // ============================================================================== public void setModelDisplayName(String newDisplayName) { String acceptableName = LDrawMPDModel .ldrawCompliantNameForName(newDisplayName); ; setModelName(acceptableName); ; }// end setModelDisplayName: // #pragma mark - // #pragma mark UTILITIES // #pragma mark - // ---------- ldrawCompliantNameForName: // ------------------------------[static]-- // // Purpose: Unfortunately, we can't accept any old input for model names. // This method accepts a user-entered string with arbitrary // characters, and returns the model name or the closest // representation thereof which is still LDraw-compliant. // // ------------------------------------------------------------------------------ public static String ldrawCompliantNameForName(String newDisplayName) { String acceptableName = null; // Since LDraw is space-delimited, we can't have whitespace at the // beginning // of the name. We'll chop of ending whitespace for good measure. acceptableName = newDisplayName.trim(); // The LDraw spec demands that the model name end with a valid LDraw // extension. Yuck! if (LDrawUtilities.isLDrawFilenameValid(acceptableName) == false) { // acceptableName = acceptableName // stringByAppendingPathExtension:"ldr"(); acceptableName = acceptableName.concat(".ldr"); } return acceptableName; }// end ldrawCompliantNameForName: // ========== lineIsMPDModelStart:modelName: // ==================================== // // Purpose: Returns if the line is a 0 FILE submodelName line. // // If it is, optionally returns the submodelName // // Note: Any line can have leading whitespace, which is why this is not // as simple as line hasPrefix:"0 FILE"] // // ============================================================================== public static boolean lineIsMPDModelStart(String line, CharBuffer modelNamePtr) { String parsedField = null; boolean isMPDModel = false; StringTokenizer strTokenizer = new StringTokenizer(line); if (strTokenizer.hasMoreTokens() == false) return false; parsedField = strTokenizer.nextToken(); if (parsedField.equals("0")) { if (strTokenizer.hasMoreTokens() == false) return false; parsedField = strTokenizer.nextToken(); if (parsedField.equals(LDrawKeywords.LDRAW_MPD_SUBMODEL_START)) isMPDModel = true; } // Strip out the MPD commands for model parsing, and read in the model // name. if (isMPDModel == true && modelNamePtr != null) { // Extract MPD-specific data: the submodel name. // Leading and trailing whitespace is ignored, in keeping with the // rules // for parsing file references (type 1 lines) if (strTokenizer.hasMoreTokens() == false) return false; modelNamePtr.append(strTokenizer.nextToken()); while (strTokenizer.hasMoreTokens()) { modelNamePtr.append(" "+strTokenizer.nextToken()); } } return isMPDModel; } // ========== lineIsMPDModelEnd: // ================================================ // // Purpose: Returns if the line is a 0 falseFILE line. // // ============================================================================== public static boolean lineIsMPDModelEnd(String line) { String parsedField = null; boolean isMPDModelEnd = false; StringTokenizer strTokenizer = new StringTokenizer(line); if (strTokenizer.hasMoreTokens() == false) return false; parsedField = strTokenizer.nextToken(); if (parsedField.equals("0")) { if (strTokenizer.hasMoreTokens() == false) return false; parsedField = strTokenizer.nextToken(); if (parsedField.equals(LDrawKeywords.LDRAW_MPD_SUBMODEL_END)) isMPDModelEnd = true; } return isMPDModelEnd; } // ========== registerUndoActions // =============================================== // // Purpose: Registers the undo actions that are unique to this subclass, // not to any superclass. // // ============================================================================== public void registerUndoActions(UndoManager undoManager) { LDrawFile enclosingFile = enclosingFile(); String oldName = modelName(); super.registerUndoActions(undoManager); // Changing the name of the model in an undo-aware way is pretty // bothersome, // because we have to track down any references to the model and change // their names too. That operation is the responsibility of the // LDrawFile, // not us. if (enclosingFile != null) { // todo // undoManager.prepareWithInvocationTarget(enclosingFile, this, // oldName); } // else // undoManager.prepareWithInvocationTarget(setModelName(oldName)); }// end registerUndoActions: // #pragma mark - // #pragma mark DESTRUCTOR // #pragma mark - }